﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NFramework.DataAccess;

namespace NFramework.UnitTests.DataAccessTests
{
    [TestClass]
    public class ReaderHelperTests : Global
    {
        private string SQL_Nulls = "SELECT * FROM ReaderHelperTests WHERE Id = 1";
        private string SQL_Defaults = "SELECT * FROM ReaderHelperTests WHERE Id = 2";
        private string SQL_RegularValues = "SELECT * FROM ReaderHelperTests WHERE Id = 3";

        [ClassInitialize]
        public static void RefreshTable(TestContext testContext)
        {
            CallEmbededResourceOnDatabase("NFramework.UnitTests.DataAccessTests.ReaderHelperTests.sql");
        }

        [TestMethod]
        [ExpectedException(typeof(System.InvalidOperationException))]
        public void UninitializedDataReaderIsNull()
        {
            using (IReaderHelper rh = ReaderHelper.Create(string.Empty))
            {
                int i = rh.Depth;
                Assert.Fail("Depth is invalid at this point");
            }
        }

        [TestMethod]
        public void IsClosedAfterDisposed()
        {
            IReaderHelper rh = null;
            using (rh = ReaderHelper.Create(SQL_Nulls))
            {
                rh.Read();
                Assert.IsFalse(rh.IsClosed);
            }
            Assert.IsTrue(rh.IsClosed);
        }

        [TestMethod]
        [ExpectedException(typeof(System.InvalidOperationException))]
        public void GetSchemaInvalidIfDataHasNotBeenRead()
        {
            using (IReaderHelper rh = ReaderHelper.Create(SQL_Nulls))
            {
                var table = rh.GetSchemaTable();
                Assert.Fail("GetSchemaTable is invalid at this point");
            }
        }

        [TestMethod]
        [ExpectedException(typeof(System.InvalidOperationException))]
        public void GetSchemaInvalidIfReaderIsClosed()
        {
            IReaderHelper rh = null;
            using (rh = ReaderHelper.Create(SQL_Nulls))
            {
                rh.Read();
            }
            var table = rh.GetSchemaTable();
            Assert.Fail("GetSchemaTable is invalid at this point");
        }

        #region [ GetBoolean ]

        [TestMethod]
        public void GetBooleanThrowsExceptionOnWrongType()
        {
            BooleanTester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetBooleanHasDefaultOnIsDBNull()
        {
            Assert.IsTrue(BooleanTester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetBooleanHasCorrectValue()
        {
            Assert.IsTrue(BooleanTester.ReturnsDesiredValue(SQL_Defaults, false));
        }

        #endregion

        #region [ GetInt16 ]

        [TestMethod]
        public void GetInt16ThrowsExceptionOnWrongType()
        {
            Int16Tester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetInt16HasDefaultOnIsDBNull()
        {
            Assert.IsTrue(Int16Tester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetInt16HasCorrectValue()
        {
            Assert.IsTrue(Int16Tester.ReturnsDesiredValue(SQL_RegularValues, 123));
        }

        #endregion

        #region [ GetInt32 ]

        [TestMethod]
        public void GetInt32ThrowsExceptionOnWrongType()
        {
            Int32Tester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetInt32HasDefaultOnIsDBNull()
        {
            Assert.IsTrue(Int32Tester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetInt32HasCorrectValue()
        {
            Assert.IsTrue(Int32Tester.ReturnsDesiredValue(SQL_RegularValues, 9999));
        }

        #endregion

        #region [ GetInt64 ]

        [TestMethod]
        public void GetInt64ThrowsExceptionOnWrongType()
        {
            Int64Tester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetInt64HasDefaultOnIsDBNull()
        {
            Assert.IsTrue(Int64Tester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetInt64HasCorrectValue()
        {
            Assert.IsTrue(Int64Tester.ReturnsDesiredValue(SQL_RegularValues, 123456789));
        }

        #endregion

        #region [ GetFloat ]

        [TestMethod]
        public void GetFloatThrowsExceptionOnWrongType()
        {
            FloatTester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetFloatHasDefaultOnIsDBNull()
        {
            Assert.IsTrue(FloatTester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetFloatHasCorrectValue()
        {
            Assert.IsTrue(FloatTester.ReturnsDesiredValue(SQL_RegularValues, 0.5f));
        }

        #endregion

        #region [ GetDouble ]

        [TestMethod]
        public void GetDoubleThrowsExceptionOnWrongType()
        {
            DoubleTester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetDoubleHasDefaultOnIsDBNull()
        {
            Assert.IsTrue(DoubleTester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetDoubleHasCorrectValue()
        {
            Assert.IsTrue(DoubleTester.ReturnsDesiredValue(SQL_RegularValues, 1.0));
        }

        #endregion

        #region [ GetDecimal ]

        [TestMethod]
        public void GetDecimalThrowsExceptionOnWrongType()
        {
            DecimalTester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetDecimalHasDefaultOnIsDBNull()
        {
            Assert.IsTrue(DecimalTester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetDecimalHasCorrectValue()
        {
            Assert.IsTrue(DecimalTester.ReturnsDesiredValue(SQL_RegularValues, 15.67m));
        }

        #endregion

        #region [ GetString ]

        [TestMethod]
        public void GetStringThrowsExceptionOnWrongType()
        {
            StringTester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetStringHasDefaultOnIsDBNull()
        {
            Assert.IsTrue(StringTester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetStringHasCorrectValue()
        {
            Assert.IsTrue(StringTester.ReturnsDesiredValue(SQL_RegularValues, "Andrew"));
        }

        #endregion

        #region [ GetGuid ]

        [TestMethod]
        public void GetGuidThrowsExceptionOnWrongType()
        {
            GuidTester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetGuidHasDefaultOnIsDBNull()
        {
            Assert.IsTrue(GuidTester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetGuidHasCorrectValue()
        {
            Assert.IsTrue(GuidTester.ReturnsDesiredValue(SQL_RegularValues, new Guid("96969346-E22E-4b08-9388-5D4F32E48E59")));
        }

        #endregion

        #region [ GetByte ]

        [TestMethod]
        public void GetByteThrowsExceptionOnWrongType()
        {
            ByteTester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetByteHasDefaultOnIsDBNull()
        {
            Assert.IsTrue(ByteTester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetByteHasCorrectValue()
        {
            Assert.IsTrue(ByteTester.ReturnsDesiredValue(SQL_RegularValues, (byte)47));
        }

        #endregion

        #region [ GetDateTime ]

        [TestMethod]
        public void GetDateTimeThrowsExceptionOnWrongType()
        {
            DateTimeTester.ThrowsExceptionOnWrongTypeInReader(SQL_Defaults);
        }

        [TestMethod]
        public void GetDateTimeHasDefaultOnIsDBNull()
        {
            Assert.IsTrue(DateTimeTester.ReturnsDefaultValueOnDBNull(SQL_Nulls));
        }

        [TestMethod]
        public void GetDateTimeHasCorrectValue()
        {
            Assert.IsTrue(DateTimeTester.ReturnsDesiredValue(SQL_RegularValues, new DateTime(2008, 5, 29)));
        }

        #endregion

        #region [ Value Testers ]

        class GetValueTester<T>
        {
            public T DefaultValue { get; set; }

            public Func<IReaderHelper, int, T> IndexGetter { get; set; }

            public Func<IReaderHelper, string, T> NameGetter { get; set; }

            public void ThrowsExceptionOnWrongTypeInReader(string sql)
            {
                using (IReaderHelper rh = ReaderHelper.Create(sql))
                {
                    while (rh.Read())
                    {
                        for (int i = 0; i < rh.FieldCount - 2; i++)
                        {
                            if (!typeof(T).Equals(rh.GetFieldType(i)))
                            {
                                try
                                {
                                    IndexGetter(rh, i);
                                    Assert.Fail(string.Format("Did not throw an exception on Field {0}", i));
                                }
                                catch (System.InvalidCastException) { }
                                try
                                {
                                    string name = rh.GetName(i);
                                    NameGetter(rh, name);
                                    Assert.Fail(string.Format("Did not throw an exception on Field {0}", name));
                                }
                                catch (System.InvalidCastException) { }
                            }
                        }
                    }
                }
            }

            public bool ReturnsDefaultValueOnDBNull(string sql)
            {
                using (IReaderHelper rh = ReaderHelper.Create(sql))
                {
                    while (rh.Read())
                    {
                        for (int i = 1; i < rh.FieldCount - 1; i++)
                        {
                            if (typeof(T).Equals(rh.GetFieldType(i)))
                            {
                                if (rh.IsDBNull(i))
                                {
                                    Assert.AreEqual(DefaultValue, IndexGetter(rh, i), string.Format("Did not get default value {0} on Field {1}", DefaultValue, i));
                                    string name = rh.GetName(i);
                                    Assert.AreEqual(DefaultValue, NameGetter(rh, name), string.Format("Did not get default value {0} on Field {1}", DefaultValue, name));
                                    return true;
                                }
                            }
                        }
                    }
                }
                return false;
            }

            public bool ReturnsDesiredValue(string sql, T desiredValue)
            {
                using (IReaderHelper rh = ReaderHelper.Create(sql))
                {
                    while (rh.Read())
                    {
                        for (int i = 1; i < rh.FieldCount - 1; i++)
                        {
                            if (typeof(T).Equals(rh.GetFieldType(i)))
                            {
                                if (!rh.IsDBNull(i))
                                {
                                    Assert.AreEqual(desiredValue, IndexGetter(rh, i), string.Format("Did not get desired value {0} on Field {1}", desiredValue, i));
                                    string name = rh.GetName(i);
                                    Assert.AreEqual(desiredValue, NameGetter(rh, name), string.Format("Did not get desired value {0} on Field {1}", desiredValue, name));
                                    return true;
                                }
                            }
                        }
                    }
                }
                return false;
            }
        }

        private GetValueTester<byte> ByteTester
        {
            get
            {
                return new GetValueTester<byte>()
                {
                    NameGetter = (rh, name) => rh.GetByte(name),
                    IndexGetter = (rh, i) => rh.GetByte(i),
                    DefaultValue = (byte)0
                };
            }
        }

        private GetValueTester<bool> BooleanTester
        {
            get
            {
                return new GetValueTester<bool>()
                {
                    NameGetter = (rh, name) => rh.GetBoolean(name),
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetBoolean(i);
                    },
                    DefaultValue = false
                };
            }
        }

        private GetValueTester<Int16> Int16Tester
        {
            get
            {
                return new GetValueTester<Int16>()
                {
                    NameGetter = (rh, name) => rh.GetInt16(name),
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetInt16(i);
                    },
                    DefaultValue = (Int16)0
                };
            }
        }

        private GetValueTester<Int32> Int32Tester
        {
            get
            {
                return new GetValueTester<Int32>()
                {
                    NameGetter = (rh, name) => rh.GetInt32(name),
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetInt32(i);
                    },
                    DefaultValue = (Int32)0
                };
            }
        }

        private GetValueTester<Int64> Int64Tester
        {
            get
            {
                return new GetValueTester<Int64>()
                {
                    NameGetter = (rh, name) => rh.GetInt64(name),
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetInt64(i);
                    },
                    DefaultValue = (Int64)0
                };
            }
        }

        private GetValueTester<float> FloatTester
        {
            get
            {
                return new GetValueTester<float>()
                {
                    NameGetter = (rh, name) => rh.GetFloat(name),
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetFloat(i);
                    },
                    DefaultValue = 0.0f
                };
            }
        }

        private GetValueTester<double> DoubleTester
        {
            get
            {
                return new GetValueTester<double>()
                {
                    NameGetter = (rh, name) => rh.GetDouble(name),
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetDouble(i);
                    },
                    DefaultValue = 0.0
                };
            }
        }

        private GetValueTester<decimal> DecimalTester
        {
            get
            {
                return new GetValueTester<decimal>()
                {
                    NameGetter = delegate(IReaderHelper rh, string name)
                    {
                        return rh.GetDecimal(name);
                    },
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetDecimal(i);
                    },
                    DefaultValue = (decimal)0.0
                };
            }
        }

        private GetValueTester<DateTime> DateTimeTester
        {
            get
            {
                return new GetValueTester<DateTime>()
                {
                    NameGetter = delegate(IReaderHelper rh, string name)
                    {
                        return rh.GetDateTime(name);
                    },
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetDateTime(i);
                    },
                    DefaultValue = DateTime.MinValue
                };
            }
        }

        private GetValueTester<string> StringTester
        {
            get
            {
                return new GetValueTester<string>()
                {
                    NameGetter = delegate(IReaderHelper rh, string name)
                    {
                        return rh.GetString(name);
                    },
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetString(i);
                    },
                    DefaultValue = null
                };
            }
        }

        private GetValueTester<Guid> GuidTester
        {
            get
            {
                return new GetValueTester<Guid>()
                {
                    NameGetter = delegate(IReaderHelper rh, string name)
                    {
                        return rh.GetGuid(name);
                    },
                    IndexGetter = delegate(IReaderHelper rh, int i)
                    {
                        return rh.GetGuid(i);
                    },
                    DefaultValue = Guid.Empty
                };
            }
        }

        #endregion

        [TestMethod]
        [ExpectedException(typeof(System.Data.SqlClient.SqlException))]
        public void ExecutingReaderOnBadTableThrowsException()
        {
            using (IReaderHelper rh = ReaderHelper.Create("SELECT * FROM ThisTableDoesNotExist"))
            {
                rh.Read();
            }
        }

        [TestMethod]
        [ExpectedException(typeof(System.Data.SqlClient.SqlException))]
        public void RollbackIsCalledWithBadTableForDefault()
        {
            ReaderHelper rh = null;
            try
            {
                using (rh = ReaderHelper.Create("SELECT * FROM ThisTableDoesNotExist"))
                {
                    rh.Read();
                }
            }
            finally
            {
                var propInfo = rh.GetType().GetProperty("Command", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                var value = propInfo.GetValue(rh, null) as System.Data.SqlClient.SqlCommand;
                Assert.IsTrue(value.CommandText.Contains("@@TRANCOUNT"));
            }
        }

        [TestMethod]
        [ExpectedException(typeof(System.Data.SqlClient.SqlException))]
        public void RollbackIsNotCalledWhenTurnedOffWithBadTableForDefault()
        {
            ReaderHelper rh = null;
            try
            {
                using (rh = ReaderHelper.Create("SELECT * FROM ThisTableDoesNotExist"))
                {
                    rh.IsRollingBackOpenTransactionsOnExecutionError = false;
                    rh.Read();
                }
            }
            finally
            {
                var propInfo = rh.GetType().GetProperty("Command", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                var value = propInfo.GetValue(rh, null) as System.Data.SqlClient.SqlCommand;
                Assert.AreEqual(rh.Procedure, value.CommandText);
            }
        }

        delegate void asserterType(int i, string name, IReaderHelper rh);
        private void IterateOverReaderHelperFields(string sql, asserterType asserter)
        {
            using (IReaderHelper rh = ReaderHelper.Create(sql))
            {
                Assert.IsTrue(rh.Read());

                for (int i = 0; i < rh.FieldCount; ++i)
                {
                    var name = rh.GetName(i);
                    asserter.Invoke(i, rh.GetName(i), rh);
                }
            }
        }

        [TestMethod]
        public void TestNullChecks()
        {
            IterateOverReaderHelperFields(SQL_Nulls, 
                delegate(int i, string name, IReaderHelper rh)
                {
                    Assert.AreEqual(rh[i], rh[name]);
                    Assert.AreEqual(rh.GetValue(i), rh.GetValue(name));
                    Assert.AreEqual(rh.IsDBNull(i), rh.IsDBNull(name));
                    Assert.AreEqual(rh.GetDataTypeName(i), rh.GetDataTypeName(name));
                    Assert.AreEqual(rh.GetFieldType(i), rh.GetFieldType(name));
                    if (i > 0) { Assert.IsTrue(rh.IsDBNull(i)); }
                }
            );
        }

        [TestMethod]
        public void TestNonNullChecks()
        {
            IterateOverReaderHelperFields(SQL_Defaults,
                delegate(int i, string name, IReaderHelper rh)
                {
                    Assert.AreEqual(rh[i], rh[name]);
                    Assert.AreEqual(rh.GetValue(i), rh.GetValue(name));
                    Assert.AreEqual(rh.IsDBNull(i), rh.IsDBNull(name));
                    Assert.AreEqual(rh.GetDataTypeName(i), rh.GetDataTypeName(name));
                    Assert.AreEqual(rh.GetFieldType(i), rh.GetFieldType(name));
                    if (i < rh.FieldCount - 2) { Assert.IsFalse(rh.IsDBNull(i)); }
                }
            );
        }

    }
}
